package dk.itu.cloudcomputing.hackystat.controller;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 *
 */
public class Controller implements HttpHandler {
    private static final String RESPONSE_CODE_ATTRIBUTE = "RESPONSE_CODE_ATTRIBUTE";
    private static HostProvider hostProvider;

    public Controller(HostProvider hostProvider) {
        Controller.hostProvider = hostProvider;
    }

    public void handle(HttpExchange httpExchange) throws IOException {
        System.out.println("\nController.handle");
        System.out.println("Request time: " + new Date());
        System.out.println("Request URI:    " + httpExchange.getRequestURI());
        System.out.println("Request method: " + httpExchange.getRequestMethod());
        System.out.println("LocalAddress:   " + httpExchange.getLocalAddress());
        System.out.println("RemoteAddress:  " + httpExchange.getRemoteAddress());
        debugHeaders(httpExchange.getRequestHeaders());

        if (requestContainsBody(httpExchange)) {
            handleRequestWithBody(httpExchange);
        } else {
            handleRequestWithoutBody(httpExchange);
        }
    }

    private boolean requestContainsBody(HttpExchange httpExchange) {
        return httpExchange.getRequestHeaders().containsKey("Content-Length");
    }

    private void handleRequestWithBody(HttpExchange httpExchange) throws IOException {
        HttpURLConnection con = getConnection(httpExchange);
        con.setRequestMethod(httpExchange.getRequestMethod());
        con.setDoInput(true);
        con.setDoOutput(true);
        addRequestHeaders(con, httpExchange);
        con.setAllowUserInteraction(true);
        int contentLength = Integer.valueOf(httpExchange.getRequestHeaders().get("Content-Length").get(0));
        System.out.println("Requestbody content length: " + contentLength);
        con.setFixedLengthStreamingMode(contentLength);

        System.out.println("Starts reading request body");
        InputStream bis = httpExchange.getRequestBody();
        byte[] reqBody = new byte[contentLength];
        //int bytesRead = bis.read(reqBody);
        int i;
        int j = 0;
        while ((i= bis.read()) != -1 && j < contentLength) {
            reqBody[j++] = (byte) i;
            //System.out.println("Read byte nr: " + j);
        }
        //System.out.println("content length from client: " + contentLength + "   requestbody bytes read: " + bytesRead);
        System.out.println("content length from client: " + contentLength + "   requestbody bytes read: " + j);
        DataOutputStream wr = new DataOutputStream(con.getOutputStream());
        wr.write(reqBody);
        wr.flush();

        System.out.println("Request sent to service");

        String response = readResponse(con, httpExchange);
        sendResponse(httpExchange, response);
    }

    private void handleRequestWithoutBody(HttpExchange httpExchange) throws IOException {
        HttpURLConnection con = getConnection(httpExchange);
        con.setRequestMethod(httpExchange.getRequestMethod());
        con.setDoInput(true);
        addRequestHeaders(con, httpExchange);
        con.connect();

        String response = readResponse(con, httpExchange);
        sendResponse(httpExchange, response);
    }

    private void sendResponse(HttpExchange httpExchange, String response) throws IOException {
        byte[] responseAsBytes = response.getBytes("UTF-8");
        Integer responsecode = (Integer) httpExchange.getAttribute(RESPONSE_CODE_ATTRIBUTE);
        httpExchange.sendResponseHeaders(responsecode != null ? responsecode : 200, responseAsBytes.length);
        DataOutputStream out = new DataOutputStream(httpExchange.getResponseBody());
        out.write(responseAsBytes);
        httpExchange.close();
    }

    private String readResponse(HttpURLConnection con, HttpExchange httpExchange) throws IOException {
        String res;
        int code = con.getResponseCode();
        if (code == 302) {
            throw new IOException("Got an unexpected redirect");
        } else if (code < 400) {
            res = readStream(con.getInputStream());
        } else {
            System.out.println("ERROR; Response Code: " + code);
            httpExchange.setAttribute(RESPONSE_CODE_ATTRIBUTE, code);
            res = readStream(con.getErrorStream());
        }

        con.disconnect();
        return res;
    }

    private String readStream(InputStream is) throws IOException {
        StringBuilder res = new StringBuilder();
        BufferedReader br;
        br = new BufferedReader(new InputStreamReader(is));
        String line;
        while ((line = br.readLine()) != null) {
            res.append(line);
        }

        return res.toString();
    }

    private void addRequestHeaders(HttpURLConnection con, HttpExchange httpExchange) {
        Headers headers = httpExchange.getRequestHeaders();
        if (headers.containsKey("Accept")) {
            con.setRequestProperty("Accept", toHeaderValueString(headers.get("Accept")));
        }

        if (headers.containsKey("Authorization")) {
            con.setRequestProperty("Authorization", toHeaderValueString(headers.get("Authorization")));
        }

        if (headers.containsKey("Content-Type")) {
            con.setRequestProperty("Content-Type", toHeaderValueString(headers.get("Content-Type")));
        }

        if (headers.containsKey("Content-Length")) {
            con.setRequestProperty("Content-Length", toHeaderValueString(headers.get("Content-Length")));
        }

    }

    private String toHeaderValueString(List<String> values) {
        StringBuilder res = new StringBuilder();
        for (String value : values) {
            res.append(value).append(", ");
        }
        return res.substring(0, res.lastIndexOf(","));
    }

    private HttpURLConnection getConnection(HttpExchange httpExchange) throws IOException {
        URI uri = httpExchange.getRequestURI();
        String pathPlusQuery = uri.getRawPath() + (uri.getRawQuery() != null ? "?" + uri.getRawQuery() : "");

        return getConnection(pathPlusQuery);
    }

    private HttpURLConnection getConnection(String path) throws IOException {
        URL sb;
        sb = new URL("http://" + getNextHost() + path);
        System.out.println("The URL: " + sb.toExternalForm());
        HttpURLConnection con;
        con = (HttpURLConnection) sb.openConnection();
        con.setConnectTimeout(2000);
        con.setReadTimeout(5000);
        con.setInstanceFollowRedirects(false);

        return con;
    }

    private String getNextHost() {
        return hostProvider.getNextHost();
    }

    private void debugHeaders(Headers headers) {
        for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
    }

    public static void main(String[] args) {
        List<String> hosts = new ArrayList<String>();
        int listenPort = 9000;
        for (int i = 0; i < args.length; i++) {
            if (args[i].equalsIgnoreCase("-port")) {
                listenPort = Integer.valueOf(args[i+1]);
                System.out.println("listen port set to " + listenPort);
            } else if (args[i].equals("-hosts")) {
                int j = i + 1;
                do {
                    hosts.add(args[j]);
                    j++;
                } while (j < args.length && !args[j].contains("-"));
                System.out.println("Hosts added: " + hosts);
            }
        }
        HostProvider hostProvider = new HostProvider();
        hostProvider.setHosts(hosts);
        Controller controller = new Controller(hostProvider);
        try {
            HttpServer server = HttpServer.create(new InetSocketAddress(listenPort), 20);
            server.createContext("/", controller);
            server.setExecutor(null);//TODO insert ThreadPoolExecuter
            server.start();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}
